using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Graphics;

#if WINDOWS_PHONE
using Microsoft.Devices.Sensors;
#elif WINDOWS
using Microsoft.Xna.Framework.Input;
#endif

namespace SPGE
{
    public class GameItemContainer : ICustomDrawable
    {
        private List<IGameItem> allItems;
        protected List<IGameItem> AllItems{
            get{return allItems;}
            set{
                allItems =value;

                background = value.FirstOrDefault(x => x is ABackground) as ABackground;

                Items = value.Where(x=>x as ABackground == null);
            }
        }


        private ABackground background = null;
        public ABackground Background
        {
            get { return background; }
            set
            {
                var items = AllItems;
                int indexOfBack = items.IndexOf(background);
                items.Remove(background);
                items.Insert(indexOfBack, value);

                background = value;
            }
        }

        public IEnumerable<IGameItem> Items
        { get; private set; }

        protected bool DynamicContainer
        { get; set; }

        public virtual float Opacity
        { get; set; }
        public virtual bool IsVisible
        { get; set; }

        #region Windows gesture simulation attributes. INCOMPLETE! => Indefinatelly delayed until version 1.0, full Windows support is not in shortterm goals.
#if WINDOWS
        List<Tuple<MouseState, GameTime>> MouseEvents = new List<Tuple<MouseState, GameTime>>();
        static TimeSpan tapDuration = TimeSpan.FromSeconds(0.6);
        static float tapDisplacementTolerance = 6f;
#endif
        #endregion

        public IEnumerable<ICollisionSource> CollitionSources
        { get; protected set; }
        public IEnumerable<IGameItem> CollitionTargets
        { get; protected set; }

        public virtual float UpdateOrder
        { get; set; }

        bool ReadTouch = false;
        bool ReadAccel = false;
#if WINDOWS_PHONE
        Accelerometer Accelerometer;
#endif

        /// <summary>
        /// Returns an integer value indicating the index of the provided item in the list of contained items in this
        /// GameItemContainer.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int IndexOf(IGameItem item)
        {
            var indexOfBack = AllItems.IndexOf(Background);
            var indexOfItem = AllItems.IndexOf(item);

            if (indexOfBack != -1)
                return indexOfItem - (indexOfItem > indexOfBack ? 1 : 0);
            else
                return indexOfItem;
        }

        /// <summary> Adds item to the GameItemContainer..</summary>
        /// <param name="item">
        ///     The item to add into the GameItemContainer
        /// </param>
        public void Add(IGameItem item)
        {
            AllItems.Add(item);
        }

        /// <summary> Removes an item from the GameItemContainer..</summary>
        /// <param name="item">
        ///     The item to remove from the GameItemContainer
        /// </param>
        /// <returns>
        ///     False if the item did not exist in the container, true if it did and it was removed.
        /// </returns>
        public bool Remove(IGameItem item)
        {
            return AllItems.Remove(item);
        }

        /// <summary> Gets the IGameItem contained, which is at the given position.</summary>
        /// <param name="index">
        ///     Zero-based index of the item to retrieve.
        /// </param>
        /// <returns>The IGameItem at the given position.</returns>
        public IGameItem GetAt(int index)
        {
            var indexOfBack = AllItems.IndexOf(Background);

            if (indexOfBack == -1)
                return AllItems[index];
            else
                return AllItems[indexOfBack > index ? index : index - 1];
        }

        public GameItemContainer(Func<IEnumerable<IGameItem>> getItems)
        {
            Opacity = 1f;

            var items = getItems();
            if (items as List<IGameItem> != null)
                AllItems = (List<IGameItem>)items;
            else
                AllItems = items.ToList();

            SetupItems();

#if WINDOWS_PHONE
            Accelerometer = new Accelerometer();
#endif
        }

        public GameItemContainer()
        {
            Opacity = 1f;

            AllItems = new List<IGameItem>(GetGameItems());

            if (!DynamicContainer)
                SetupItems();

#if WINDOWS_PHONE
            Accelerometer = new Accelerometer();
#endif
        }

        private void SetupItems()
        {
            var cs = new List<ICollisionSource>();
            foreach (var source in RecursiveGetItems(true).Where(x => x is ICollisionSource))
                cs.Add(source as ICollisionSource);

            CollitionSources = cs;

            CollitionTargets = RecursiveGetItems(true).Where(x => x is ICollisionTarget);

#if WINDOWS_PHONE
            if (AllItems.Any(x => x is IAccelerometerAndTouchUpdateable || x is IAccelerometerUpdatable) || this is IAccelerometerAndTouchUpdateable || this is IAccelerometerUpdatable)
            {
                ReadAccel = true;
            }
            if (AllItems.Any(x => x is IAccelerometerAndTouchUpdateable || x is ITouchUpdatable) || this is IAccelerometerAndTouchUpdateable || this is ITouchUpdatable)
                ReadTouch = true;
#elif WINDOWS
            ReadAccel = false;

            if (AllItems.Any(x => x is ITouchUpdatable) || this is ITouchUpdatable)
                ReadTouch = true;
#endif
        }

        /// <summary>
        /// This setups the scene to whatever it is.
        /// </summary>
        protected virtual IEnumerable<IGameItem> GetGameItems()
        {
            return new List<IGameItem>();
        }

        public virtual void Initialize()
        {
            foreach (var item in AllItems)
                item.Initialize();
        }

        public IEnumerable<IGameItem> RecursiveGetItems(bool includeContainers)
        {
            foreach (var item in Items)
                if (item as GameItemContainer == null)
                    yield return item;
                else
                {
                    foreach (var inner in ((GameItemContainer)item).RecursiveGetItems(includeContainers))
                        yield return inner;

                    if (includeContainers)
                        yield return item;
                }
        }

        public virtual void Load(ResourceGroupHandler gm)
        {
            foreach (var item in AllItems)
                item.Load(gm);

            if (ReadAccel)
            {
#if WINDOWS_PHONE
                Accelerometer.Start();
#elif WINDOWS
                //No accelerometer support yet.
#endif
            }
        }

        public virtual void Unload()
        {
            foreach (var item in AllItems)
                item.Unload();
        }

        internal void BaseUpdate(Microsoft.Xna.Framework.GameTime gameTime)
        {
            TouchCollection? collection = null;
            List<GestureSample> gestures = null;

            if (ReadTouch)
            {
                collection = TouchPanel.GetState();
                gestures = new List<GestureSample>();
                while (TouchPanel.IsGestureAvailable)
                    gestures.Add(TouchPanel.ReadGesture());

                #region Windows gesture simulation
#if WINDOWS
                var mouse = Mouse.GetState();

                if (mouse.LeftButton == ButtonState.Pressed)
                    MouseEvents.Add(new Tuple<MouseState, GameTime>(mouse, gameTime));
                else if (mouse.LeftButton == ButtonState.Released && MouseEvents.Any(x => x.Item1.LeftButton == ButtonState.Pressed))
                {
                    //Find and remove corresponding event.
                    var down = MouseEvents.First(x => x.Item1.LeftButton == ButtonState.Pressed);
                    MouseEvents.Remove(down);

                    var displacement = new Vector2(mouse.X - down.Item1.X, mouse.Y - down.Item1.Y);

                    if (gameTime.TotalGameTime - down.Item2.TotalGameTime <= tapDuration && displacement.Length() < tapDisplacementTolerance)
                        //If time between press and depress is less than tapDuration and the distance between the events is less than tapDisplacementTolerance then this counts as a click
                        gestures.Add(new GestureSample(GestureType.Tap, gameTime.TotalGameTime, new Vector2(mouse.X, mouse.Y), new Vector2(mouse.X, mouse.Y), displacement, displacement));
                }

#endif
                #endregion
            }

            Action<IEnumerable<IGameItem>> updateItems = null;

            updateItems = new Action<IEnumerable<IGameItem>>((IEnumerable<IGameItem> items) =>
                {
                    //Simply iterate and update all items.
                    foreach (var item in items.OrderBy(x => x.UpdateOrder))
                    {
                        if (!item.IsActive)
                            continue;

                        if (item.OnUpdateStart != null)
                            item.OnUpdateStart(item);

                        if (item is GameItemContainer)
                        {
                            if ((item as GameItemContainer).DynamicContainer)
                                SetupItems();

                            updateItems((item as GameItemContainer).Items);
                        }

                        if (item is ISimpleUpdatable)
                            (item as ISimpleUpdatable).Update(gameTime);
#if WINDOWS_PHONE
                        if (item is IAccelerometerUpdatable)
                            (item as IAccelerometerUpdatable).Update(gameTime, Accelerometer.CurrentValue);
                        if (item is IAccelerometerAndTouchUpdateable)
                            (item as IAccelerometerAndTouchUpdateable).Update(gameTime, Accelerometer.CurrentValue, collection.Value, gestures);
#endif
                        if (item is ITouchUpdatable)
                            (item as ITouchUpdatable).Update(gameTime, collection.Value, gestures);

                        if (item.OnUpdateEnd != null)
                            item.OnUpdateEnd(item);
                    }
                });

            updateItems(AllItems);

            if (this is ISimpleUpdatable)
                (this as ISimpleUpdatable).Update(gameTime);
#if WINDOWS_PHONE
            if (this is IAccelerometerUpdatable)
                (this as IAccelerometerUpdatable).Update(gameTime, Accelerometer.CurrentValue);
            if (this is IAccelerometerAndTouchUpdateable)
                (this as IAccelerometerAndTouchUpdateable).Update(gameTime, Accelerometer.CurrentValue, collection.Value, gestures);
#endif
            if (this is ITouchUpdatable)
                (this as ITouchUpdatable).Update(gameTime, collection.Value, gestures);

            foreach (var target in CollitionTargets)
                if ((target as ICollisionTarget).IsActive)
                {
                    var cTarget = (target as ICollisionTarget);

                    (target as ICollisionTarget).DetectCollitionWith(CollitionSources.Where(x=>x.IsActive));
                }
        }

        public virtual void Draw(Microsoft.Xna.Framework.GameTime gameTime, Microsoft.Xna.Framework.Graphics.SpriteBatch sb)
        {
            bool sbStarted = false;
            foreach (var item in AllItems.OrderByDescending(x => x.RenderLevel))
            {
                if (!item.IsVisible)
                    continue;

                var oldOpacity = item.Opacity;

                item.Opacity *= Opacity;

                if (item.SpriteUse == SpriteBatchUse.AlreadyBeganSpriteBatch && !sbStarted)
                {
                    sb.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
                    sbStarted = true;
                }
                if (item.SpriteUse == SpriteBatchUse.EndedSpriteBatch && sbStarted)
                {
                    sb.End();
                    sbStarted = false;
                }

                item.Draw(gameTime, sb);

                item.Opacity = oldOpacity;
            }

            if (sbStarted)
                sb.End();
        }
    }
}
